﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace Eventless.WinForms
{
    public static class ControlBindings
    {
        public static TResult Extend<TResult, TControl>(this TControl control, string key, 
                                                        Func<TResult> make)
            where TControl : Control
        {
            var map = control.Tag as Dictionary<string, object>;
            if (map == null)
                control.Tag = map = new Dictionary<string, object>();

            object obj;
            if (!map.TryGetValue(key, out obj))
                obj = map[key] = make();    
                
            return (TResult)obj;
        }

        public static void InvokeIfRequired(this Control ctrl, Action action)
        {
            if (ctrl.InvokeRequired)
                ctrl.Invoke(action);
            else
                action();
        }

        public static IWriteable<bool> IsEnabled(this Control control)
        {
            return control.Extend("enabled", () =>
                {
                    var obs = new Writeable<bool>(control.Enabled);
                    obs.Changed += () => control.InvokeIfRequired(() => control.Enabled = obs.Value);
                    control.EnabledChanged += (sender, args) => obs.Value = control.Enabled;
                    return obs;
                });
        }

        public static IWriteable<string> Text(this TextBox textBox)
        {
            return textBox.Extend("text", () =>
                {
                    var obs = new Writeable<string>(textBox.Text);
                    obs.Changed += () => textBox.InvokeIfRequired(() => textBox.Text = obs.Value);
                    textBox.TextChanged += (sender, args) => obs.Value = textBox.Text;
                    return obs;
                });
        }

        public static IWriteable<bool> IsChecked(this CheckBox checkBox)
        {
            return checkBox.Extend("checked", () =>
                {
                    var obs = new Writeable<bool>(checkBox.Checked);
                    obs.Changed += () => checkBox.InvokeIfRequired(() => checkBox.Checked = obs.Value);
                    checkBox.CheckedChanged += (sender, args) => obs.Value = checkBox.Checked;
                    return obs;
                });
        }

        public static IWriteable<CheckState> CheckState(this CheckBox checkBox)
        {
            return checkBox.Extend("checkState", () =>
                {
                    var obs = new Writeable<CheckState>(checkBox.CheckState);
                    obs.Changed += () => checkBox.InvokeIfRequired(() => checkBox.CheckState = obs.Value);
                    checkBox.CheckStateChanged += (sender, args) => obs.Value = checkBox.CheckState;
                    return obs;
                });
        }

        public static IWriteableList<CheckedListBoxItem> Items(this CheckedListBox checkedListBox)
        {
            return checkedListBox.Extend("items", () =>
                {
                    var obs = new WriteableList<CheckedListBoxItem>(
                        checkedListBox.Items.OfType<CheckedListBoxItem>());

                    obs.Added += index =>
                        {
                            var item = obs[index].Bind(checkedListBox);
                            checkedListBox.InvokeIfRequired(() => checkedListBox.Items.Insert(index, item));
                        };
                    obs.Updated += index =>
                        {
                            var item = obs[index].Bind(checkedListBox);
                            checkedListBox.InvokeIfRequired(() => checkedListBox.Items[index] = item);
                        };
                    obs.Removed += index =>
                        {
                            var old = checkedListBox.Items[index] as CheckedListBoxItem;
                            if (old != null)
                                old.Unbind(checkedListBox);
                            checkedListBox.InvokeIfRequired(() => checkedListBox.Items.RemoveAt(index));
                        };
                    obs.Cleared += () =>
                        {
                            foreach (var old in checkedListBox.Items.OfType<CheckedListBoxItem>())
                                old.Unbind(checkedListBox);
                            checkedListBox.InvokeIfRequired(() => checkedListBox.Items.Clear());
                        };

                    checkedListBox.ItemCheck += (s, ev) => obs[ev.Index].State.Value = ev.NewValue;
                    return obs;
                });
        }
    }
}
